﻿/*///////////////////////////////////////////////////////////
TodoEntry.as
Ben Smith
2009
///////////////////////////////////////////////////////////*/

package com.bsmith.app.presentation
{
	import com.bsmith.app.model.TodoAppState;
	import com.bsmith.app.model.TodoEvent;
	import com.bsmith.app.model.TodoItem;
	import com.bsmith.common.CustomEvent;
	import com.bsmith.common.ui.Checkbox;
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.FocusEvent;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.filters.ColorMatrixFilter;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
	import flash.utils.Timer;
	
	/*===========================================================
	TodoEntry
	An entry in the todo roll
	===========================================================*/
	public class TodoEntry extends Sprite
	{
		static public const EVENT_BEGIN_DRAG:String = "te.bdrg";
		static public const EVENT_END_DRAG:String = "te.edrg";
		
		public var _stxItem:TextField;
		public var _smcHighlight:MovieClip;
		public var _smcCheckbox:Checkbox;
		
		private var _cItem:TodoItem = null;			//Todo item
		private var _bEditting:Boolean = false;		//Is this being editted?
		private var _bDragging:Boolean = false;		//Is this being dragged?
		private var _nMouseOffset:Number = 0;		//Offset from drag point to item origin
		
		/*-----------------------------------------------------------
		TodoEntry - ctor
		-----------------------------------------------------------*/
		public function TodoEntry(item:TodoItem) 
		{
			if (this.stage)
				initialize();
			else	
				this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage, false, 0, true);
			
			_cItem = item;
			_cItem.addEventListener(TodoItem.EVENT_CHANGED, onItemChanged, false, 0, true);
			updateView();
		}
		
		/*-----------------------------------------------------------
		onAddedToStage
		Initialize the entry
		-----------------------------------------------------------*/
		private function onAddedToStage(e:Event):void 
		{
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			initialize();
		}
		
		/*-----------------------------------------------------------
		initialize
		Sets up entry
		-----------------------------------------------------------*/
		private function initialize():void
		{
			_smcHighlight.mouseChildren = false;
			_smcHighlight.doubleClickEnabled = true;
			_stxItem.doubleClickEnabled = true;
		
			this.addEventListener(MouseEvent.DOUBLE_CLICK, onEditTextBegin, true, 0, true);
			this.addEventListener(MouseEvent.MOUSE_DOWN, onBeginDrag, false, 0, true);
			this.addEventListener(MouseEvent.MOUSE_UP, onEndDrag, false, 0, true);
			_smcCheckbox.addEventListener(Checkbox.EVENT_TOGGLED_ON, onCheckedTodo, false, 0, true);
			_smcCheckbox.addEventListener(Checkbox.EVENT_TOGGLED_OFF, onUncheckedTodo, false, 0, true);
			
			_stxItem.selectable = false;
			_smcHighlight.visible = false;
		}
		
		/*-----------------------------------------------------------
		onBeginDrag
		Begin dragging the entry
		-----------------------------------------------------------*/
		private function onBeginDrag(e:MouseEvent):void 
		{
			if (_bEditting)
				return;
			this.addEventListener(Event.ENTER_FRAME, doDrag, false, 0, true);
			_bDragging = true;
			_nMouseOffset = this.stage.mouseY - this.y;
			this.dispatchEvent(new CustomEvent(EVENT_BEGIN_DRAG, {entry:this}));
		}
		
		/*-----------------------------------------------------------
		doDrag
		Handle dragging action
		-----------------------------------------------------------*/
		private function doDrag(e:Event):void 
		{
			//Reorder entries
			doDragReorder();
			
			//Perform drag
			this.y = this.stage.mouseY - _nMouseOffset; 
			if (this.parent.mouseY < 0 || this.parent.mouseY > this.parent.height 
				|| this.parent.mouseX < 0 || this.parent.mouseX > this.parent.width)
				onEndDrag();
		}
		
		/*-----------------------------------------------------------
		onEndDrag
		End dragging the entry
		-----------------------------------------------------------*/
		private function onEndDrag(e:MouseEvent=null):void 
		{
			if (!_bDragging)
				return;
			this.removeEventListener(Event.ENTER_FRAME, doDrag);
			_bDragging = false;
			doDragReorder();
			this.dispatchEvent(new CustomEvent(EVENT_END_DRAG, {entry:this}));
		}
		
		/*-----------------------------------------------------------
		doDragReorder
		Reorders todo entries based on Y location
		-----------------------------------------------------------*/
		private function doDragReorder():void
		{
			var clips:Array = TodoRoll(this.parent).getClips();
			var items:Array = [];
			clips.sort(compareY);
			for each (var clip:TodoEntry in clips)
				items.push(clip.getItem());
			if (this.parent)
				TodoAppState.instance.getTodoList(TodoRoll(this.parent).getListType()).reorderItems(items);
		}
		
		/*-----------------------------------------------------------
		compareY
		Compares the y properties of 2 display objects
		-----------------------------------------------------------*/
		private function compareY(obj1:DisplayObject, obj2:DisplayObject):int
		{
			return obj1.y - obj2.y;
		}
		
		/*-----------------------------------------------------------
		onItemChanged
		Item has changed
		-----------------------------------------------------------*/
		private function onItemChanged(e:Event):void 
		{
			finishEdit();
			updateView();
		}
		
		/*-----------------------------------------------------------
		updateView
		Updates entry view
		-----------------------------------------------------------*/
		private function updateView():void
		{
			_stxItem.text = _cItem.entry;
			_smcCheckbox.setChecked(_cItem.completed, false);
			this.alpha = _cItem.completed ? 0.5 : 1;
		}
		
		/*-----------------------------------------------------------
		onEditTextBegin
		Turn the text to be selectable
		-----------------------------------------------------------*/
		private function onEditTextBegin(e:MouseEvent):void 
		{
			_stxItem.selectable = true;
			_stxItem.stage.focus = _stxItem;
			_stxItem.setSelection(0, _stxItem.text.length);
			_smcHighlight.visible = true;
			
			
			var tf:TextFormat = _stxItem.getTextFormat();
			tf.color = 0x000000;
			_stxItem.defaultTextFormat = tf;
			_stxItem.setTextFormat(tf);
			
			this.stage.addEventListener(MouseEvent.CLICK, checkFinishedEditMouse, false, 0, true);
			this.stage.addEventListener(FocusEvent.FOCUS_OUT, onLoseFocus, false, 0, true);
			_stxItem.addEventListener(KeyboardEvent.KEY_DOWN, checkFinishedEditKeyboard, false, 0, true);
			
			_bEditting = true;
		}
		
		/*-----------------------------------------------------------
		onLoseFocus
		When the program loses focus, finish editting
		-----------------------------------------------------------*/
		private function onLoseFocus(e:FocusEvent):void 
		{
			finishEdit();
		}
		
		/*-----------------------------------------------------------
		checkFinishedEditKeyboard
		Has the user finished editting an entry via keyboard commands?
		-----------------------------------------------------------*/
		private function checkFinishedEditKeyboard(e:KeyboardEvent):void 
		{
			if (e.keyCode != Keyboard.ENTER)
				return;
			finishEdit();
		}
		
		/*-----------------------------------------------------------
		checkFinishedEditMouse
		Has the user finished editting an entry via mouse commands?
		-----------------------------------------------------------*/
		private function checkFinishedEditMouse(e:MouseEvent):void 
		{
			if (e.target == _stxItem || e.target == this)
				return;
			finishEdit();
		}
		
		/*-----------------------------------------------------------
		finishEdit
		Finishes editting an entry
		-----------------------------------------------------------*/
		private function finishEdit():void
		{
			if (!_bEditting)
				return;
				
			_smcHighlight.visible = false;
			_stxItem.setSelection(0, 0);
			_stxItem.selectable = false;
			_stxItem.stage.focus = null;
			
			var tf:TextFormat = _stxItem.getTextFormat();
			tf.color = 0xffffff;
			_stxItem.defaultTextFormat = tf;
			_stxItem.setTextFormat(tf);
			
			this.stage.removeEventListener(FocusEvent.FOCUS_OUT, onLoseFocus);
			_stxItem.removeEventListener(KeyboardEvent.KEY_DOWN, checkFinishedEditKeyboard);
			this.stage.removeEventListener(MouseEvent.CLICK, checkFinishedEditMouse);
			
			_bEditting = false;
			_cItem.entry = _stxItem.text;
		}
		
		/*-----------------------------------------------------------
		onCheckedTodo
		When the user checks the checkbox, set the item as completed
		-----------------------------------------------------------*/
		private function onCheckedTodo(e:Event)
		{
			_cItem.completed = true;
		}
		
		/*-----------------------------------------------------------
		onUncheckedTodo
		When the user checks the checkbox, set the item as completed
		-----------------------------------------------------------*/
		private function onUncheckedTodo(e:Event)
		{
			_cItem.completed = false;
		}
		
		/*-----------------------------------------------------------
		release
		Releases entry
		-----------------------------------------------------------*/
		public function release()
		{
			this.removeEventListener(MouseEvent.DOUBLE_CLICK, onEditTextBegin);
			this.removeEventListener(MouseEvent.MOUSE_DOWN, onBeginDrag);
			this.removeEventListener(MouseEvent.MOUSE_UP, onEndDrag);
			_smcCheckbox.removeEventListener(Checkbox.EVENT_TOGGLED_ON, onCheckedTodo);
			_smcCheckbox.removeEventListener(Checkbox.EVENT_TOGGLED_OFF, onUncheckedTodo);
			
			_cItem.removeEventListener(TodoItem.EVENT_CHANGED, onItemChanged);
			_cItem = null;
			
			finishEdit();
			onEndDrag();
		}
		
		/*###########################################################
		GETTERS AND SETTERS
		###########################################################*/
		private function getClipIndex():int { return TodoRoll(this.parent).getClips().indexOf(this); }
		public function getItem():TodoItem { return _cItem; }
		public function isDragging():Boolean { return _bDragging; }
		public function setBright(enabled:Boolean)
		{
			const brightness:Number = -20;
			if (!enabled)
			{
				_smcBG.filters = [];
			}
			else
			{
				var mat:Array = [];
				mat = mat.concat([1,0,0,0,brightness]);
				mat = mat.concat([0,1,0,0,brightness]);
				mat = mat.concat([0,0,1,0,brightness]);
				mat = mat.concat([0,0,0,1,brightness]);
				_smcBG.filters = [new ColorMatrixFilter(mat)];
			}
		}
	}
}